winbrew_windows\deployment\msix/
remove.rs1use anyhow::{Context, Result};
2
3use windows::ApplicationModel::Package;
4use windows::Management::Deployment::PackageManager;
5use windows::core::HSTRING;
6
7pub fn remove(package_full_name: &str) -> Result<()> {
13 let package_manager = PackageManager::new().context("failed to create package manager")?;
14
15 package_manager
16 .RemovePackageAsync(&HSTRING::from(package_full_name))
17 .with_context(|| format!("failed to start uninstall for {package_full_name}"))?
18 .join()
19 .with_context(|| format!("msix uninstall failed for {package_full_name}"))?;
20
21 Ok(())
22}
23
24pub(crate) fn matching_package_full_names(
25 package_manager: &PackageManager,
26 package_name: &str,
27) -> Result<Vec<HSTRING>> {
28 let normalized_name = package_name.trim().to_ascii_lowercase();
29 let mut matching_full_names = Vec::new();
30
31 if let Ok(package) = package_manager.FindPackageByPackageFullName(&HSTRING::from(package_name))
32 && package_matches(&package, &normalized_name)?
33 {
34 matching_full_names.push(package_full_name(&package)?);
35 }
36
37 for package in package_manager
38 .FindPackagesByPackageFamilyName(&HSTRING::from(package_name))
39 .context("failed to enumerate installed packages")?
40 {
41 if package_matches(&package, &normalized_name)? {
42 matching_full_names.push(package_full_name(&package)?);
43 }
44 }
45
46 matching_full_names.sort_by_key(|value| value.to_string());
47 matching_full_names.dedup();
48
49 Ok(matching_full_names)
50}
51
52fn package_matches(package: &Package, expected_name: &str) -> Result<bool> {
53 let package_id = package.Id().context("failed to read package identity")?;
54
55 Ok(identity_matches(
56 &package_id
57 .Name()
58 .context("failed to read package name")?
59 .to_string(),
60 &package_id
61 .FamilyName()
62 .context("failed to read package family name")?
63 .to_string(),
64 &package_id
65 .FullName()
66 .context("failed to read package full name")?
67 .to_string(),
68 expected_name,
69 ))
70}
71
72fn package_full_name(package: &Package) -> Result<HSTRING> {
73 package
74 .Id()
75 .context("failed to read package identity")?
76 .FullName()
77 .context("failed to read package full name")
78}
79
80fn identity_matches(name: &str, family_name: &str, full_name: &str, expected_name: &str) -> bool {
81 [name, family_name, full_name]
82 .into_iter()
83 .any(|value| value.eq_ignore_ascii_case(expected_name))
84}
85
86#[cfg(test)]
87mod tests {
88 use super::identity_matches;
89
90 #[test]
91 fn identity_matches_name_family_or_full_name() {
92 assert!(identity_matches(
93 "Contoso.App",
94 "Contoso.App_123abc",
95 "Contoso.App_123abc!App",
96 "contoso.app"
97 ));
98 assert!(identity_matches(
99 "Contoso.App",
100 "Contoso.App_123abc",
101 "Contoso.App_123abc!App",
102 "contoso.app_123abc"
103 ));
104 assert!(identity_matches(
105 "Contoso.App",
106 "Contoso.App_123abc",
107 "Contoso.App_123abc!App",
108 "contoso.app_123abc!app"
109 ));
110 }
111
112 #[test]
113 fn identity_matches_rejects_other_names() {
114 assert!(!identity_matches(
115 "Contoso.App",
116 "Contoso.App_123abc",
117 "Contoso.App_123abc!App",
118 "fabrikam.tool"
119 ));
120 }
121}